<?php

abstract class NCore_Page
{
    private $pages = array();
    private $ports = array();
    private $pipes = array();
    private $requestedPipe = NULL;
    private $portGroups = array();
    private $requestedPort = NULL;
    private $requestedPortGroup = NULL;
    private $datasources = array();
    private $actions = array();
    private $mode = N::MODE_STANDARD;
    private $markup;
    private $auth;
    private $portFactory = NULL;

    public function getMode()
    {
        return $this->mode;
    }

    // Requested pipe
    public function getRequestedPipe()
    {
        return $this->requestedPipe;
    }

    // Markup
    public function setMarkup($markup)
    {
        $this->markup = $markup;
    }

    public function getMarkup()
    {
        return $this->markup;
    }

    // Ports
    public function addPort($value)
    {
        if ($value instanceof NCore_IPort)
        {
            return $this->ports[$value->getName()] = $this->processPortFactory($value);
        }
        else
        {
            return $this->ports[$value] = $this->processPortFactory(new NCore_CharDataPort($value));
        }
    }

    public function addGetPort($name)
    {
        $p = new NCore_GPCSPort($name);

        return $this->ports[$name] = $this->processPortFactory($p->withGlobalArray($_GET, 'g'));
    }

    public function addPostPort($name)
    {
		$p = new NCore_GPCSPort($name);

        return $this->ports[$name] = $this->processPortFactory($p->withGlobalArray($_POST, 'p'));
    }

    public function addCookiePort($name)
    {
		$p = new NCore_GPCSPort($name);

        return $this->ports[$name] = $this->processPortFactory($p->withGlobalArray($_COOKIE, 'c'));
    }

    public function addSessionPort($name)
    {
		$p = new NCore_GPCSPort($name);

        return $this->ports[$name] = $this->processPortFactory($p->withGlobalArray($_SESSION, 's'));
    }

    public function addFilePort($name)
    {
        $p = new NCore_FilePort($name);

        return $this->ports[$name] = $this->processPortFactory($p);
    }

    public function addCompositePort($name, $components)
    {
        $newComponents = array();

        $portCount = 0;

        foreach ($components as $comp) {
            if ($comp instanceof NCore_CompositePort) {
                $portCount++;
                $newComponents[] = $comp;
            } else {
                if (array_key_exists($comp, $this->ports)) {
                    $portCount++;
                    $newComponents[] = $this->getPort($comp);
                } else {
                    $newComponents[] = $comp;
                }
            }
        }

        if ($portCount < 2) throw new Exception('You are using a composite port with less than 2 component ports.  You should try using the formatter capabilities of ports instead.');

        return $this->ports[$name] = new NCore_CompositePort($name, $newComponents);
    }

    public function getPort($string)
    {
        if (array_key_exists($string, $this->ports)) {
            return $this->ports[$string];
        } else {
            throw new Exception('There are no ports that match the name \''.$string.'\'');
        }
    }

    public function getAllPorts()
    {
        return $this->ports;
    }

    public function getPortRow()
    {
        $portValues = new NCore_Row();

        $ports = func_get_args();

        foreach ($ports as $portName) {
            $portValues[$portName] = $this->getPort($portName);
        }

        return $portValues;
    }

    // Portgroups
    public function addPortGroup($groupName, $portNames)
    {
        $this->portGroups[$groupName] = $portNames;

        return $this;
    }

    public function getPortGroup($groupName)
    {
        return $this->portGroups[$groupName];
    }

    public function getPortGroupRow($groupName)
    {
        $row = new NCore_Row();

        foreach ($this->getPortGroup($groupName) as $portName) {
            $row[$portName] = $this->getPort($portName);
        }

        return $row;
    }

    public function getAllPortGroups()
    {
        return $this->portGroups;
    }

    // Pipes
    public function addPipe($value)
    {
        if ($value instanceof NCore_Pipe)
        {
            return $this->pipes[$value->getName()] = $value;
        }
        else
        {
            return $this->pipes[$value] = new NCore_Pipe($value);
        }
    }

    // Deprecated so this method can in the future apply to actions; Should now use addActionPipe(name) method
    public function addAction($value)
    {
        if ($value instanceof NCore_ActionPipe)
        {
            return $this->pipes[$value->getName()] = $value;
        }
        else
        {
            return $this->pipes[$value] = new NCore_ActionPipe($value);
        }
    }

    public function addActionPipe($value)
    {
        if ($value instanceof NCore_ActionPipe)
        {
            return $this->pipes[$value->getName()] = $value;
        }
        else
        {
            return $this->pipes[$value] = new NCore_ActionPipe($value);
        }
    }

    public function addABPipe($value)
    {
        if ($value instanceof NCore_ABPipe)
        {
            return $this->pipes[$value->getName()] = $value;
        }
        else
        {
            return $this->pipes[$value] = new NCore_ABPipe($value);
        }
    }
    
    public function getPipe($string)
    {
        if (array_key_exists($string, $this->pipes)) {
            return $this->pipes[$string];
        } else {
            throw new Exception('There are no pipes that match the name \''.$string.'\'');
        }
    }

    public function getAllPipes()
    {
        return $this->pipes;
    }

    // Datasources
    public function addDatasource($value)
    {
        if ($value instanceof NCore_IDatasource)
        {
            return $this->datasources[$value->getName()] = $value;
        } 
        else
        {
            throw new Exception('The addDatasource() method only accepts instances of NCore_IDatasource.');
        }
    }

    public function addSQLDatasource($value)
    {
        if ($value instanceof NCore_SQLDatasource)
        {
            return $this->datasources[$value->getName()] = $value;
        }

        return $this->datasources[$value] = new NCore_SQLDatasource($value);
    }

    public function addRSSDatasource($value)
    {
        if ($value instanceof NCore_RSSDatasource)
        {
            return $this->datasources[$value->getName()] = $value;
        }

        return $this->datasources[$value] = new NCore_RSSDatasource($value);
    }

    public function getDatasource($name)
    {
        return $this->datasources[$name];
    }

    public function addSQLAction($value)
    {
        if ($value instanceof NCore_IAction)
        {
            return $this->actions[$value->getName()] = $value;
        }

        return $this->actions[$value] = new NCore_SQLAction($value);
    }

    public function addSQLActionGateway($value)
    {
        if ($value instanceof NCore_IAction)
        {
            return $this->actions[$value->getName()] = $value;
        }

        return $this->actions[$value] = new NCore_SQLActionGateway($value);
    }

    public function beginSQLTransaction()
    {
        return NCore_SQLAction::beginSQLTransaction();
    }

    public function commitSQLTransaction()
    {
        return NCore_SQLAction::commitSQLTransaction();
    }

    public function rollbackSQLTransaction()
    {
        return NCore_SQLAction::rollbackSQLTransaction();
    }

    public function process()
    {
        $this->authorize();

        $this->setPortFactory();

        $this->handlePostRequest();

        $this->integratePagesFromConfig();

        $this->init();

        $this->setNestedPipes();

        $this->determineMode();

        if ($this->getMode() == N::MODE_VALIDATE) {
            $this->databindValidationRequest();
        } else {
            $this->databindPipes();

            foreach ($this->pages as $page) {
                $this->markup = $page->lastCall($this->markup);

                if (is_null($this->markup)) {
                    throw new Exception('The lastCall() method did not return a value.  Are you sure you remembered to return the markup value from the method?');
                }
            }

            $this->markup = $this->lastCall($this->markup);

            if (is_null($this->markup)) {
                throw new Exception('The lastCall() method did not return a value.  Are you sure you remembered to return the markup value from the method?');
            }
        }

        return $this->output();
    }

    public function authorize()
    {
        if (count(N::config('protected_directories')) < 1) return;

        $this->auth = new NCore_Page_Auth();

        if (!$this->auth->isAuthorized()) $this->redirect($this->auth->getLogin());
    }

    public function setPortFactory()
    {
        if (!is_null(N::config('port_factory')))
        {
            $fact = N::config('port_factory');
            return $this->portFactory = new $fact;
        }
    }

    public function processPortFactory(NCore_IPort $port)
    {
        if (is_null($this->portFactory))
        {
            return $port;
        }
        else
        {
            return $this->portFactory->addPortSettings($port);
        }
    }

    public function handlePostRequest()
    {
        if ((count($_POST) > 0) && (count($_FILES) < 1) && (N::config('post_request_handling') == N::AUTO_REDIRECT_POSTS_WO_FILES))
        {
            $this->savePostVarsToSession();
            $this->setRedirectFlag();
            $this->redirectToCurrentURL();
        }
        else if((count($_POST) > 0) && (count($_FILES) > 0) && (N::config('post_request_handling') == N::AUTO_REDIRECT_ALL_POSTS))
        {
            /*
             * still have to add, don't do redirect for now
            $this->saveFileVarsToSession();
            $this->saveFilesToTempDir();
            $this->setRedirectFlag();
            $this->redirectToCurrentURL();
             *
             */
        }
        else if($_COOKIE['nephtali_post_redirect'] == 1)
        {
            $this->startSession();

            if (isset($_SESSION['nephtali_post_vars']))
            {
                $this->createPostVarsFromSession();
            }

            $this->unsetRedirectFlag();
        }
    }

    public function savePostVarsToSession()
    {
        $this->startSession();
        
        foreach ($_POST as $key=>$value)
        {
            $_SESSION['nephtali_post_vars'][$key] = $value;
            unset($_POST[$key]);
        }
    }

    public function createPostVarsFromSession()
    {
        $this->startSession();

        foreach ($_SESSION['nephtali_post_vars'] as $key=>$value)
        {
            $_POST[$key] = $value;
            unset($_SESSION['nephtali_post_vars'][$key]);
        }
    }

    public function setRedirectFlag()
    {
        setCookie('nephtali_post_redirect', 1);
    }

    public function unsetRedirectFlag()
    {
        setCookie('nephtali_post_redirect', 0);
    }

    public function integratePagesFromConfig()
    {
        if (count(N::config('integrated_pages')) < 1) return;

        // find current path
        $currentPath = nephtali_remove_base_url_directory($_SERVER['PHP_SELF']);

        // check if current directory has integrated pages
        foreach (N::config('integrated_pages') as $dir => $pages) {
            if (strpos($currentPath, $dir) === 0) {
                foreach ($pages as $page) {
                    $this->withIntegratedPages(new $page);
                }
            }
        }
    }

    public function init()
    {
    }

    public function setNestedPipes()
    {
        foreach ($this->pipes as $pipe)
        {
            if (!is_null($pipe->getNestedPipe()))
            {
                $this->getPipe($pipe->getNestedPipe())->isNestedPipe(true);
            }
        }
    }

    public function determineMode()
    {
        if (isset($_GET['nmode'])) {
            $nmode = $_GET['nmode'];

            if (!preg_match(N::REGEX_KEY, $nmode)) {
                throw new Exception('We could not process the mode you requested.');
            }

            switch ($nmode) {
                case 'htmlfrag' :
                    $this->mode = N::MODE_HTMLFRAG;

                    if (isset($_GET['npipe'])) {
                        if (!preg_match(N::REGEX_KEY, $_GET['npipe']))
                        {
                            throw new Exception('We could not process the pipe you requested.');
                        }
                        
                        if ($this->getPipe($_GET['npipe'])->isNestedPipe())
                        {
                            throw new Exception('The pipe '.$_GET['npipe'].' cannot be retrieved with a REST-ful htmlfrag request because it is a nested pipe and it depends on the context of the rows of its containing pipe.');
                        }

                        $this->requestedPipe = $_GET['npipe'];

                    } else {
                        throw new Exception('Your htmlfrag request could not be processed because you did not include the npipe GET variable, which should contain the name of the requested pipe.');
                    }

                    break;

                case 'validate' :      
                    $this->mode = N::MODE_VALIDATE;

                    if (isset($_GET['nportgroup'])) {
                        if (!preg_match(N::REGEX_KEY, $_GET['nportgroup'])) {
                            throw new Exception('We did not receive a valid port group name.');
                        }

                        $this->requestedPortGroup = $_GET['nportgroup'];

                        break;
                    }

                    if (isset($_GET['nport'])) {
                        if (!preg_match(N::REGEX_KEY, $_GET['nport'])) {
                            throw new Exception('We did not receive a valid port name.');
                        }

                        $this->requestedPort = $_GET['nport'];

                        break;
                    } else {
                        throw new Exception('We did not receive a port group, a required GET variable for validation requests.');
                    }

                default:
                    throw new Exception('The nmode GET variable must be set to \'htmlfrag\' or \'validate\'.');
            }
        } else {
            $this->mode = N::MODE_STANDARD;
        }
    }

    // hook for implementation in subclasses
    public function lastCall($markup)
    {
        return $markup;
    }

    private function output()
    {
        // prepend any additional output to markup as this is an easy way to debug
        $this->markup .= ob_get_contents();

        ob_clean();

        if (N::config('debug') == N::APPEND_DEBUG_INFO && N::getMode() != 'test') {
            $this->markup.= $this->getDebugInfo();
        }

        return trim($this->markup);
    }
    public function integratePages()
    {
        $pages = func_get_args();
        call_user_func_array(array($this, 'withIntegratedPages'), $pages);
    }

    public function withIntegratedPages()
    {
        $pages = func_get_args();

        foreach($pages as $page)
        {
            if($page instanceof NCore_Page)
            {
                $this->pages[] = $page;

                $page->init();

                foreach ($page->getAllPorts() as $port) {
                    $this->addPort($port);
                }

                foreach ($page->getAllPortGroups() as $name => $value) {
                    $this->addPortGroup($name, $value);
                }

                foreach($page->getAllPipes() as $pipe)
                {
                    $this->addPipe($pipe);
                }
            } else {
                throw new Exception ('Not all of the arguments to the integratePages() method were subclasses of NCore_Page.');
            }
        }
    }
    
    public function portGroupIsSet($groupName)
    {
        if (count($this->getPortGroup($groupName)) < 1) throw new Exception('The portgroup '.$groupName.' does not contain any ports.  Are you sure you set this portgroup?');

        foreach ($this->getPortGroup($groupName) as $portName) {
            $port = $this->getPort($portName);

            if(!$port->isPresent()) return false;
        }

        return true;
    }

    public function validatePorts()
    {
        $portNames = func_get_args();

        $dataset = new NCore_Dataset();

        foreach ($portNames as $portName) {
            if ($this->ports[$portName] instanceof NCore_IPort) {
                if (!$this->ports[$portName]->validate()) {
                    $dataset[] = new NCore_Row(array('name'=>$portName, 'message'=>$this->ports[$portName]->getErrorMsg()));
                }
            } else {
                throw new Exception('The object \''.$portName.'\' is not an instance of NCore_Port and cannot be validated using the openPorts() method.');
            }
        }

        return $dataset;
    }

    public function validatePortGroup($portGroup)
    {
        $dataset = new NCore_Dataset();

        $pg = $this->getPortGroup($portGroup);

        foreach ($pg as $portName) {
            if ($this->ports[$portName] instanceof NCore_IPort) {
                if (!$this->ports[$portName]->validate()) {
                    $dataset[] = new NCore_Row(array('name'=>$portName, 'message'=>$this->ports[$portName]->getErrorMsg()));
                }
            } else {
                throw new Exception('The object \''.$portName.'\' is not an instance of NCore_Port and cannot be validated using the openPorts() method.');
            }
        }

        return $dataset;
    }

    protected function databindActionPipes()
    {
        foreach ($this->pipes as $pipe) {
            if ($pipe instanceof NCore_ActionPipe) {
                // if it is an action port, cycle through the actions and see if any of the port groups are set
                // if a port group is set, render it and break out of loop to check for any other actions.
                foreach ($pipe->getActions() as $action) {
                    $pipe->withRequiredPortGroup($action);

                    $method = $action.ucfirst($pipe->getName());

                    if ($this->portGroupIsSet($pipe->getRequiredPortGroup())) {
                        if ($pipe->fill($this->validatePortGroup($pipe->getRequiredPortGroup()))) {
                            $this->databind($pipe->withSelectedView('feedback'));
                            break;
                        }

                        $tempPipe = $this->$method($pipe);

                        // because nulls are allowed in typehinting (http://ch2.php.net/language.oop5.typehinting)
                        // we must manually check for this
                        if (is_null($tempPipe)) {
                            throw new Exception('The pipe \''.$pipe->getName().'\' was not returned from the method '.$method.'.');
                        }

                        $this->databind($tempPipe);

                        break;
                    }
                }
                if (!$pipe->getRendered()) {
                    $this->databind($pipe->withSelectedView('empty'));
                }
            }
        }
    }
    
    protected function databindStandardPipes()
    {
        foreach ($this->pipes as $pipe) {
            if (!$pipe->isNestedPipe())
            {
                $this->autoValidateAndDatabind($pipe);
            }
        }
    }

    protected function databindPipes()
    {
        if ($this->getMode() == N::MODE_STANDARD) {
            // first databind action pipes, which databind if particular portgroups are set
            $this->databindActionPipes();

            // second, databind standard pipes
            $this->databindStandardPipes();
        } else {
            $pipe = $this->getPipe($this->getRequestedPipe());

            $this->autoValidateAndDatabind($pipe);
        }
    }

    protected function autoValidateAndDatabind(NCore_Pipe $pipe)
    {
        // already finished if rendered
        if ($pipe->getRendered()) {
            return;
        }

        // take care of required pipes first
        foreach ($pipe->getRequiredPipes() as $reqPipeName) {
            $this->autoValidateAndDatabind($this->getPipe($reqPipeName));
        }

        $method = 'select'.ucfirst($pipe->getName()).'View';

        if (is_null($pipe->getRequiredPortGroup())) {
            $tempPipe = $this->$method($pipe);

            // because nulls are allowed in typehinting (http://ch2.php.net/language.oop5.typehinting)
            // we must manually check for this
            if (is_null($tempPipe)) {
                throw new Exception('The pipe \''.$pipe->getName().'\' was not returned from the method '.$method.'.');
            }

            $this->databind($tempPipe);
        } else {
            if ($pipe->fill(
                    $this->validatePortGroup($pipe->getRequiredPortGroup())
            )) {
                $this->databind($pipe->withSelectedView('feedback'));
            } else {
                $tempPipe = $this->$method($pipe);

                // because nulls are allowed in typehinting (http://ch2.php.net/language.oop5.typehinting)
                // we must manually check for this
                if (is_null($tempPipe)) {
                    throw new Exception('The pipe '.$pipe->getName().' failed to return itself from the method '.$method.'.');
                }

                $this->databind($tempPipe);
            }
        }
    }

    protected function autoValidateAndDatabindNestedPipe(NCore_Pipe $pipe, $markup, NCore_Row $row)
    {
        if (count($pipe->getRequiredPipes()) > 0)
            throw new Exception('Nested pipes are not allowed to declare required pipes due to complicated rendering problems.');

        $method = 'select'.ucfirst($pipe->getName()).'View';

        if (is_null($pipe->getRequiredPortGroup())) {
            $tempPipe = $this->$method($pipe, $row);

            // because nulls are allowed in typehinting (http://ch2.php.net/language.oop5.typehinting)
            // we must manually check for this
            if (is_null($tempPipe)) {
                throw new Exception('The pipe \''.$pipe->getName().'\' was not returned from the method '.$method.'.');
            }

            return $this->databindPipe($tempPipe, $markup);
        } else {
            if ($pipe->fill(
                    $this->validatePortGroup($pipe->getRequiredPortGroup())
            )) {
                return $this->databindPipe($pipe->withSelectedView('feedback'));
            } else {
                $tempPipe = $this->$method($pipe, $row);

                // because nulls are allowed in typehinting (http://ch2.php.net/language.oop5.typehinting)
                // we must manually check for this
                if (is_null($tempPipe)) {
                    throw new Exception('The pipe '.$pipe->getName().' failed to return itself from the method '.$method.'.');
                }

                return $this->databindPipe($tempPipe, $markup);
            }
        }
    }

    protected function databind(NCore_Pipe $pipe)
    {
        $this->setMarkup($this->databindPipe($pipe, $this->markup));

        $pipe->withRendered(TRUE);
    }

    protected function databindValidationRequest()
    {
        if (!is_null($this->requestedPortGroup)) {
            $pipe = $this->addPipe('validation');

            $portNames = $this->getPortGroup($this->requestedPortGroup);

            // validate the required ports
            $pipe->fill(call_user_func_array(array($this, 'validatePorts'), $portNames));

            return $this->setMarkup($this->databindPipe($pipe, $this->markup));
        }

        if (!is_null($this->requestedPort)) {
            $pipe = $this->addPipe('validation');

            // validate the required ports
            $pipe->fill(call_user_func_array(array($this, 'validatePorts'), array($this->requestedPort)));

            return $this->setMarkup($this->databindPipe($pipe, $this->markup));
        }

        throw new Exception('Validation requests require either a nport -or- nportgroup.');
    }

    public function getDebugInfo()
    {
        return N::_(new NCore_Page_Debugger())->getDebugInfo($this->ports);
    }

    public function redirect($url)
    {
        ob_clean();
        header('Location: '.$url);
        exit();
    }

    public function redirectToCurrentURL()
    {
        $this->redirect($this->getCurrentURL());
    }

    public function getCurrentURL()
    {
        if (isset($_SERVER['SCRIPT_URI']))
        {
            $url = $_SERVER['SCRIPT_URI'];

            if (count($_GET))
            {
                $url .= '?';
                foreach ($_GET as $key=>$value)
                {
                    $url .= $key.'='.$value.'&';
                }
            }

            return $url;
        }

        $url = '';
        $port = '';

        if (isset($_SERVER['HTTP_HOST']))
        {
            $colon = strpos($_SERVER['HTTP_HOST'], ':');

            if ($colon === false)
            {
                if (isset($_SERVER['SERVER_PORT']))
                {
                    $port = ':' . $_SERVER['SERVER_PORT'];
                }

                $url = $_SERVER['HTTP_HOST'];

            }
            else
            {
                $url = substr($_SERVER['HTTP_HOST'], 0, $colon);
                $port = substr($_SERVER['HTTP_HOST'], $colon);
            }
        }
        elseif(isset($_SERVER['SERVER_NAME']))
        {
            $url = $_SERVER['SERVER_NAME'];
            if (isset($_SERVER['SERVER_PORT']))
            {
                $port = ':' . $_SERVER['SERVER_PORT'];
            }
        }

        if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on')
        {
            $url = 'https://' . $url;
            if ($port == ':443')
            {
                $port = '';
            }
        }
        else
        {
            $url = 'http://' . $url;
            if ($port == ':80')
            {
                $port = '';
            }
        }

        $url .= $port;

        if (isset($_SERVER['REQUEST_URI']))
        {
            $url .= $_SERVER['REQUEST_URI'];
        }
        else
        {
            throw Exception('We could not retrieve the URL for this page, so you\'ll have to turn off post reloading in nconfig.');
        }

        if (count($_GET))
        {
            $url .= '?';
            foreach ($_GET as $key=>$value)
            {
                $url .= $key.'='.$value.'&';
            }
        }

        return $url;
    }

    // String replace that encodes output for safety
    public function replace($search, $replace, $subject, $whitelist = NULL)
    {
        return N::safe_str_replace($search, $replace, $subject, $whitelist);
    }

    // Add session and regenerate id
    public function addSession($name, $value, $encrypted = false, $login = false)
    {
        $this->startSession();

        if ($login) {
            session_regenerate_id();
        }

        if ($encrypted) {
            return N::encryptedSession($name, $value);
        } else {
            return $_SESSION[$name] = $value;
        }
    }

    public function getConfig($name)
    {
        return N::config($name);
    }

    public function encrypt($value)
    {
        return N::encrypt($value);
    }

    public function decrypt($value)
    {
        return N::decrypt($value);
    }

    public function startSession()
    {
        if (isset($_SESSION)) return;

        session_start();
    }

	protected function escape($string, $allowedHTML)
	{
		// use htmlspecialchars because it only does the 5 most important chars, and doesn't mess them up
		$string = htmlspecialchars($string, ENT_QUOTES, 'UTF-8');

		foreach($allowedHTML as $allowed) {
			if ($this->isRegex($allowed))
			{
				// Save entitied version of regex
				// This should be acceptable because <, >, &, ', ", don't have special meaning in regexes
				$entitied_allowed = htmlspecialchars($allowed, ENT_QUOTES, 'UTF-8');

				// replace instances of entitied needle with unentitied form
				$string = preg_replace_callback($entitied_allowed, array($this, 'removeEntities'), $string);
			}
			else
			{
				// Save entitied version of tag, then look for occurances of it in string and replace them
				$entitied_allowed = htmlspecialchars($allowed, ENT_QUOTES, 'UTF-8');
				$string = str_replace($entitied_allowed, $allowed, $string);
			}
		}

		return $string;
	}

	protected function isRegex($s)
	{
		// check if the string is surrounded by '/' chars
		if ((strpos($s, '/') == 0) && (strrpos($s, '/') == (strlen($s)-1)))
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	protected function removeEntities($s)
	{
		return html_entity_decode($s[0]);
	}

    protected function databindPipe(NCore_Pipe $pipe, $markup)
    {
        if ($this->mode == N::MODE_STANDARD)
        {
            return implode('', $this->databindStandardRequest($pipe, $markup));
        }

        if ($this->mode == N::MODE_HTMLFRAG && !$pipe->isNestedPipe())
        {
            $pagesections = $this->databindStandardRequest($pipe, $markup);
            return $pagesections[1];
        }

        if ($this->mode == N::MODE_HTMLFRAG && $pipe->isNestedPipe())
        {
            return implode('', $this->databindStandardRequest($pipe, $markup));
        }

        if ($this->mode == N::MODE_VALIDATE)
        {
            return $this->databindJSONRequest($pipe);
        }
    }

    protected function databindStandardRequest($pipe, $markup)
    {
		// get pipe markup
        $pageSections = $this->getSections('pipe', $pipe, $markup);

		// get view markup
        $views = $this->getSections('view', $pipe, $pageSections[1]);

        if ($pipe instanceof NCore_ActionPipe)
        {
            $actions = $this->getSections('action', $pipe, $pipe->getView()->getMarkup());

            if (count($actions) == 3)
            {
                $pipe->getView()->withMarkup($actions[1]);
            }
            else if (count($actions) > 0 && count($actions) != 1)
            {
                throw new Exception('The action \''.$pipe->getRequiredPortGroup().'\' within the \''.$pipe->getName().'\' pipe does not have matching opening and closing tags.');
            }
        }

		// databinding methods called
		foreach($pipe->getView()->getDatabindingMethods() as $method)
		{
			$pipe->getView()->withMarkup($method->call($pipe->getView()->getMarkup(), $pipe->getDataset()));
		}

		//----New, now allow pipes to be built from outside in because we always know which data region we're talking about
		// check if it has a data region
        $data = $this->getSections('data', $pipe, $pipe->getView()->getMarkup());

		// if it has a data region, only databind that region
		if(count($data) == 3)
		{
			$pageSections[1] = $data[0].$this->databindRows($data[1], $pipe->getDataset(), $pipe->getView(), $pipe).$data[2];
		}
		// otherwise, just output the code without databinding
		else
		{
			$pageSections[1] = $pipe->getView()->getMarkup();
		}

		// databound methods called
		foreach($pipe->getView()->getDataboundMethods() as $method)
		{
			$pageSections[1] = $method->call($pageSections[1], $pipe->getDataset());
        }

        $pipe->getView()->withMarkup($pageSections[1]);

		return $pageSections;
    }

	protected function databindRows($markup, $dataset, $view, $pipe)
	{
		$mappings = $view->getMappings($dataset);
		$whitelists = $view->getWhitelists();

		// check to make sure datasource has items
		if(count($dataset) < 1) return $markup;

		// cycle through the array of rows in this particular pipe
		foreach($dataset as $row)
		{
			$section = $markup;

			// rowdatabinding methods called
			foreach($view->getRowDatabindingMethods() as $method)
			{
				$section = $method->call($section, $row);
			}

            // handle any nested pipes
            if (!is_null($pipe->getNestedPipe()))
            {
                $section = $this->autoValidateAndDatabindNestedPipe($this->getPipe($pipe->getNestedPipe()), $section, $row);
            }

			// cycle through the individual keys of this row
			foreach($row as $label=>$data)
			{
				// check to make sure this is an expected key
				if(array_key_exists($label, $mappings))
				{
					if(array_key_exists($label, $whitelists))
					{
						$escapedData = $this->escape($data, $whitelists[$label]);
					}
					else
					{
						$escapedData = $this->escape($data, array());
					}

					$section = str_replace($mappings[$label], $escapedData, $section);
				}
			}

			// rowdatabound methods called
			foreach($view->getRowDataboundMethods() as $method)
			{
				$section = $method->call($section, $row);
			}

			$newSections[]=$section;
		}

		$markup = implode('', $newSections);

		return $markup;
	}

	protected function databindJSONRequest(NCore_Pipe $pipe, $markup)
	{
        // return a JSON array for use
		if(count($pipe->getDataset()) < 1)
		{
			return '[]';
		}
		else
		{
			$invalidValues = '[';
			foreach($pipe->getDataset() as $row)
			{
				$invalidValues .= '["'.$row['name'].'","'.str_replace('"', '\"', $row['message']).'"]';
			}
			return $invalidValues.']';
		}
	}

    protected function getSections($sectionName, $pipe, $markup)
    {
        switch ($sectionName)
        {
            case 'pipe':
                // get section
                $sections = explode('<!--pipe:'.$pipe->getName().'-->', $markup);

                // check to make sure there were opening and closing tags
                if (count($sections) != 3) throw new Exception('The pipe \''.$pipe->getName().'\' does not have matching opening and closing tags.');

                return $sections;

            case 'view':
                // get view markup
                $sections = explode('<!--view:'.$pipe->getSelectedView().'-->', $markup);

                // check to make sure there were opening and closing tags
                if (count($sections) == 3)
                {
                    $pipe->getView()->withMarkup($sections[1]);
                }

                // -------NEED to make this extensible to multiple levels of nested pipes.  Currently only handles one nested pipe.
                // check if there were 5, and if the pipe has a nested pipe, use the middle three sections
                if (count($sections) == 5)
                {
                    $pipe->getView()->withMarkup($sections[1].'<!--view:'.$pipe->getSelectedView().'-->'.$sections[2].'<!--view:'.$pipe->getSelectedView().'-->'.$sections[3]);
                }

                // error out if there is no markup
                if (is_null($pipe->getView()->getMarkup()))
                {
                    throw new Exception('The view \''.$pipe->getSelectedView().'\' does not have matching opening and closing tags in the markup AND it doesn\'t have markup set programmatically via $view->withMarkup().  Please set the markup by one of the afore-mentioned means.');
                }

                return $sections;
            case 'action':
                $sections = explode('<!--action:'.$pipe->getRequiredPortGroup().'-->', $pipe->getView()->getMarkup());
                return $sections;
            case 'data':
                //----New, now allow pipes to be built from outside in because we always know which data region we're talking about
                // check if it has a data region
                $sections = explode('<!--data:'.$pipe->getName().'-->', $pipe->getView()->getMarkup());

                // check to make sure there were opening and closing tags
                if ((count($sections) != 3))
                {
                    // check if it has a data region
                    $sections = explode('<!--data-->', $pipe->getView()->getMarkup());

                    // check to make sure there were opening and closing tags
                    if ((count($sections) != 3) && (count($sections) != 1)) throw new Exception('The data region for the view \''.$pipe->getSelectedView().'\' does not have matching opening and closing tags of the format &lt;!--data--&gt; or &lt;!--data:viewname--&gt;.');
                }

                return $sections;
        }
    }

    function __call($method, $args)
    {
        if (count($args) == 1 && $args[0] instanceof NCore_Pipe) {
            $argPipe = $args[0];

            foreach ($this->pages as $page) {
                if (array_key_exists($argPipe->getName(), $page->getAllPipes()))

                return $page->$method($argPipe);
            }

            throw new Exception('There is no method called \''.$method.'\'(). on this instance of the NCore_Page class.');
        } else {
            throw new Exception('There is no method called \''.$method.'\'(). on this instance of the NCore_Page class.');
        }
    }
}

?>